import glob
import os.path
import re
import shutil
import loguru
from log_config import logger
import openpyxl
import openpyxl.cell
import pandas as pd
from tabulate import tabulate
from ruamel.yaml import YAML

import os
import pandas as pd
from tabulate import tabulate
from openpyxl import load_workbook
from openpyxl.styles import PatternFill, Alignment
from openpyxl.styles import Font
import mantiDB
# maven_version_pattern = r'^(\d+\.\d+\.\d+|\d+\.\d+|\d+:\d+\.\d+\.\d+)$'
# maven_version_pattern = r'^\d+\.\d+\.\d+(-\d+)?$'
maven_version_pattern = r'^(?:\d+:\d+\.\d+\.\d+|\d+\.\d+\.\d+|\d+\.\d+|\d+)(?:[-.]\w+)*$'
maven_remove_version_pattern = r'\w+:\w+(:\d+\.\d+\.\d+)'


# def read_yaml_file(file_path):
#     with open(file_path, 'r') as file:
#         data = yaml.safe_load(file)
#     return data
def read_yaml_file(path):
    with open(path, 'r') as file:
        yaml = YAML(typ='safe', pure=True)
        data = yaml.load(file)
    return data

def parse_spec_file(spec_path, os_version, maven_only):
    spec_file = glob.glob(spec_path)[0]
    with open(spec_file, 'r', encoding='utf-8') as f:
        spec_content = f.read()

    build_requires = parse_spec_attribute("BuildRequires", spec_content, os_version)

    real_rpms = []
    extra_rpms = ['chrpath', 'dos2unix', 'desktop-file-utils', 'sed', 'coreutils', 'findutils', 'xmvn', 'java']
    # extra_rpms = ['chrpath', 'dos2unix', 'desktop-file-utils', 'sed', 'coreutils', 'findutils', 'xmvn', 
    #               'cmake', 'gdb', 'make', 'gcc', 'gcc-c++', 'findutils', 'ninja-build','dbus-x11', 'xorg-x11-server-Xvfb']
    
    # if True:
        # 将虚包转换为实包
    for virtual_rpm in build_requires:
        real_rpm_list = mantiDB.search_rpm_from_virtual(virtual_rpm, os_version)
        if len(real_rpm_list) == 0:
            logger.info(f"{virtual_rpm} is not valid in spec")
        elif len(real_rpm_list) > 1:
            logger.info(f"{virtual_rpm} has many names in spec")
        else:
            real_rpm = real_rpm_list[0]
            # 排除多余的依赖包
            if real_rpm not in extra_rpms:
                if not maven_only:
                    if real_rpm == 'gettext-devel':
                        real_rpm = 'gettext'
                    real_rpms.append(real_rpm)
                else:
                    real_rpms.append(virtual_rpm)


    real_rpms = list(set(real_rpms))
    # 排除子包：
    # qt5-doctools本身是 qt5-qttools 的子包w
    # TODO:暂时未处理
    return real_rpms

def parse_spec_attribute(attribute_name, spec_content, os_version):
    pattern = fr'^{attribute_name}:\s+(.*)$'
    matches = re.findall(pattern, spec_content, flags=re.MULTILINE)
    # if matches:
    #     dependencies = []
    #     for match in matches:
    #         # 使用正则表达式来匹配依赖项及其版本号
    #         # dependency_list = re.findall(r'\S+(?:\s*[><]?=\s*\S+(?:\s+\S+)*)?', match)
    #         # 使用正则表达式来匹配依赖项，忽略版本号及其后的内容
    #         dependency_list = re.findall(r'\S+(?:\s*[><]?=\s*\S+(?:\s+\S+)*)?', match)
    #         dependencies.extend(dependency_list)
    #     return [dep.strip() for dep in dependencies]
    # else:
    #     return []
    if matches:
        dependencies = []
        for match in matches:
            # 使用正则表达式来匹配依赖项，忽略版本号及其后的内容
            dependency_list = re.findall(r'([^\s><=]+)', match)
            dependencies.extend(dependency_list)
        # 提取每个依赖项的 RPM 包名部分并去除多余逗号、引号、空格
        rpm_names = []
        for dep in dependencies:
            # 如果遇到变量形式 %{xxx}，直接忽略
            if dep.startswith('%{') and not dep.endswith('}'):
                continue
            elif re.match(maven_version_pattern, dep.strip()):
                # print('excluded!')
                continue
            else:
                rpm_name_cleaned = dep.strip(',"\',').strip()
                match = re.match(maven_remove_version_pattern, dep.strip())
                if match:
                    version = match.group(1)
                    rpm_name_cleaned = rpm_name_cleaned.replace(version, '')
                print(f'output:{rpm_name_cleaned}!')
                if rpm_name_cleaned not in rpm_names:
                    rpm_names.append(rpm_name_cleaned)
        return rpm_names
    else:
        return []


def check(rpm_list, spec_rpm_list, maven_only):

    data = []
    for rpm in spec_rpm_list:
        if rpm in rpm_list:
            data.append([rpm, "√"])
        else:
            #maven的java命名千奇百怪，就不一一适配了
            if maven_only and rpm == 'java' or 'java-' in rpm:
                rpm_list.append(rpm)
                data.append([rpm, "√"])
            else:
                data.append([rpm, "×"])
    return data

def check_for_maven(rpm_list, spec_rpm_list, maven_only, os_version):
    data = []
    for rpm in spec_rpm_list:
        real_rpm_list = mantiDB.search_rpm_from_virtual(rpm, os_version)
        # if len(real_rpm_list) == 0:
        #     loguru.logger.info(f"{rpm} is not valid in spec for maven")
        #     continue
        # elif len(real_rpm_list) > 1:
        #     loguru.logger.info(f"{rpm} has many names in spec for maven")
        #     continue
        # else:
        real_rpm = real_rpm_list[0]

        if real_rpm in rpm_list:
            data.append([rpm, "√"])
        else:
            #maven的java命名千奇百怪，就不一一适配了
            if maven_only and rpm == 'java' or 'java-' in rpm:
                rpm_list.append(rpm)
                data.append([rpm, "√"])
            else:
                data.append([rpm, "×"])
    
    return data


def calculate_precision_recall(rpm_list, spec_rpm_list, os_version):
    real_spec_rpm_list = []
    for rpm in spec_rpm_list:
        real_rpm_list = mantiDB.search_rpm_from_virtual(rpm, os_version)
        real_rpm = real_rpm_list[0]
        real_spec_rpm_list.append(real_rpm)
            

    # tp = len(set(rpm_list) & set(spec_rpm_list))
    # fp = len(set(rpm_list) - set(spec_rpm_list))
    # fn = len(set(spec_rpm_list) - set(rpm_list))
    tp = len(set(rpm_list) & set(real_spec_rpm_list))
    fp = len(set(rpm_list) - set(real_spec_rpm_list))
    fn = len(set(real_spec_rpm_list) - set(rpm_list))
    # logger.info(f"len(set(rpm_list)) = {len(set(rpm_list))}, len(set(real_spec_rpm_list)) = {len(set(real_spec_rpm_list))}, len(set(spec_rpm_list)) = {len(set(spec_rpm_list))}")
    # logger.info(f"tp:{tp}, fp:{fp}")

    recall = tp / (tp + fn) if (tp + fn) > 0 else 0
    precision = tp / (tp + fp) if (tp + fp) > 0 else 0

    return precision, recall


def reverse_parse_spec(spec_rpm_list, spec_path, os_version, maven_only):
    spec_file = glob.glob(spec_path)[0]
    with open(spec_file, 'r', encoding='utf-8') as f:
        spec_content = f.read()
    
    valid_build_requires_map = {}
    build_requires = parse_spec_attribute("BuildRequires", spec_content, os_version)
    for build_require in build_requires:
        real_rpm_list = mantiDB.search_rpm_from_virtual(build_require, os_version)
        if len(real_rpm_list) == 0:
            logger.info(f"{build_require} is not valid")
            continue
        elif len(real_rpm_list) > 1:
            logger.info(f"{build_require} has many names")
            continue
        else:
            real_rpm = real_rpm_list[0]
            
            
        if real_rpm in spec_rpm_list:
            valid_build_requires.append(build_require)
    
    valid_build_requires = list(set(valid_build_requires))
    return valid_build_requires

    # real_rpms = []
    # if not maven_only:
    # # if True:
    #     # 将虚包转换为实包
    #     for virtual_rpm in build_requires:
    #         real_rpm_list = mantiDB.search_rpm_from_virtual(virtual_rpm, os_version)
    #         if len(real_rpm_list) == 0:
    #             loguru.logger.info(f"{virtual_rpm} is not valid")
    #         elif len(real_rpm_list) > 1:
    #             loguru.logger.info(f"{virtual_rpm} has many names")
    #         else:
    #             real_rpm = real_rpm_list[0]
    #             # 排除多余的依赖包
    #             extra_rpms = ['chrpath', 'dos2unix', 'desktop-file-utils', 'sed', 'coreutils', 'findutils', 'xmvn']
    #             if real_rpm not in extra_rpms:
    #                 if real_rpm == 'gettext-devel':
    #                     real_rpm = 'gettext'
    #                 real_rpms.append(real_rpm)
    #     # print(f"{len(build_requires)} ?= {len(real_rpms)}")
    #     # assert (len(build_requires) == len(real_rpms))
    # else:
    #     real_rpms = build_requires
    # real_rpms = list(set(real_rpms))
    # # 排除子包：
    # # qt5-doctools本身是 qt5-qttools 的子包
    # # TODO:暂时未处理
    # return real_rpms



def estimate_result(result_path, spec_path, output_excel, os_version, maven_only):
    dependencies = read_yaml_file(result_path)
    spec_rpm_list = parse_spec_file(spec_path, os_version, maven_only)
    # print("len of spec_rpm_list:", len(spec_rpm_list))
    # reverse_search_list = reverse_parse_spec(spec_rpm_list, spec_path, os_version, maven_only)
    # print("len of reverse_search_list:", len(reverse_search_list))
    data = []

    rpm_list = []
    for condition, rpms in dependencies.items():
        rpm_list += rpms
    


    if maven_only:
        data += check_for_maven(rpm_list, spec_rpm_list, maven_only, os_version)
    else:
        data += check(rpm_list, spec_rpm_list, maven_only)
    # data += check(rpm_list, reverse_search_list, maven_only)
    precision, recall = calculate_precision_recall(rpm_list, spec_rpm_list, os_version)
    # precision, recall = calculate_precision_recall(rpm_list, reverse_search_list)
    headers = ['Project Name', 'RPM Name', 'Is Found']
    logger.info(f"estimate <{result_path}>  with <{spec_path}>")
    # print(f"estimate <{result_path}>  with <{spec_path}>")
    table = tabulate(data, headers=headers[1:], tablefmt='fancy_grid')
    # print(tabulate(data, headers=headers[1:], tablefmt='fancy_grid'))
    logger.info("\n" + table)

    # print(f"Precision: {precision:.2f}")
    logger.info(f"Precision: {precision:.3f}")
    # print(f"Recall: {recall:.2f}")
    logger.info(f"Recall: {recall:.2f}")

    # 获取项目名称
    directory, _ = os.path.split(spec_path)
    _, spec_name = os.path.split(directory)

    # 将项目名称添加到数据中
    data_with_project = [[spec_name] + row for row in data]

    # 将结果保存为Excel文件
    df = pd.DataFrame(data_with_project, columns=headers)
    df['Precision'] = precision
    df['Recall'] = recall


    # 检查文件是否存在
    if os.path.exists(output_excel):
        # 如果文件存在，则读取现有数据
        with pd.ExcelWriter(output_excel, mode='a', engine='openpyxl', if_sheet_exists='overlay') as writer:
            book = writer.book
            sheet = book['Results']
            # 获取当前行数
            start_row = sheet.max_row + 1

            # 在现有数据后添加两行黄色背景的空行
            yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
            for _ in range(2):
                sheet.append([''] * (len(headers) + 2))  # +2 to account for the additional precision and recall columns
                for cell in sheet[start_row]:
                    cell.fill = yellow_fill
                start_row += 1

            # 将新数据附加到现有数据后
            df.to_excel(writer, sheet_name='Results', startrow=start_row - 1, index=False, header=False)
    else:
        # 如果文件不存在，创建新文件并写入数据
        with pd.ExcelWriter(output_excel, mode='w', engine='openpyxl') as writer:
            df.to_excel(writer, sheet_name='Results', index=False)

    print(f"Results have been saved to {output_excel}")


def merge_and_center_cells(excel_file):
    wb = openpyxl.load_workbook(excel_file)
    sheet = wb.active

    # 获取需要处理的列的最大列数和最大行数
    max_row = sheet.max_row
    max_column = max(sheet.max_column, 5)  # 最多处理到第5列

    # 遍历第1、4、5列
    for col in [1, 4, 5]:
        current_value = None
        current_fill = None
        start_row = 1
        for row in range(1, max_row + 1):
            cell = sheet.cell(row=row, column=col)
            value = cell.value
            fill = cell.fill  # 获取单元格背景颜色
            yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")

            # 判断单元格是否有文本内容
            if value != current_value and fill != current_fill:
                if start_row < row - 1:
                    sheet.merge_cells(start_row=start_row, start_column=col, end_row=row - 1, end_column=col)
                current_value = value
                current_fill = fill
                start_row = row

        # 处理最后一组相同值
        if start_row < max_row:
            sheet.merge_cells(start_row=start_row, start_column=col, end_row=max_row, end_column=col)

    # 设置全局居中显示
    for row in sheet.iter_rows():
        for cell in row:
            cell.alignment = Alignment(horizontal='center', vertical='center')

    # 保存修改后的Excel文件
    wb.save(excel_file)
    print(f"Cells merged and centered in {excel_file}")


def calculate_total_results(excel_file):
    if not os.path.exists(excel_file):
        logger.error(f"文件 {excel_file} 不存在")
        return

    wb = openpyxl.load_workbook(excel_file)
    sheet = wb.active

    last_row = sheet.max_row
    last_column = max(sheet.max_column, 5)
    check_cell = sheet.cell(last_row - 1, last_column)

    # 读取 Excel 文件
    df = pd.read_excel(excel_file, sheet_name='Results')
    count_values = df['Is Found'].value_counts()
    average_precision = df['Precision'].mean()

    df = pd.read_excel(excel_file, sheet_name='Results')
    count_found = df['Is Found'].value_counts().get('√', 0)
    count_missed = df['Is Found'].value_counts().get('×', 0)

    total_recall = count_found / (count_found + count_missed) if (count_found + count_missed) > 0 else 0
    print(f"Count of found: {count_found}")
    print(f"Count of missed: {count_missed}")
    print(f"Total Recall: {total_recall:.4f}")
    print(f"Average Precision: {average_precision:.4f}")

    headers = ['√ count', '× count', 'Total recall']
    values = [count_found, count_missed, total_recall]

    # 检查是否需要写入标题
    if check_cell.value != 'Total recall':
        bold_font = Font(bold=True)  # 设置加粗字体
        for col_offset, header in enumerate(headers):
            cell = sheet.cell(row=last_row + 2, column=last_column + 1 + col_offset, value=header)
            cell.font = bold_font
            sheet.cell(row=last_row + 2, column=last_column + 1 + col_offset, value=header)
        for col_offset, value in enumerate(values):
            sheet.cell(row=last_row + 3, column=last_column + 1 + col_offset, value=value)
        print(f"Total recall has been saved to {excel_file}")
    else:
        for col_offset, value in enumerate(values):
            sheet.cell(row=last_row, column=last_column + 1 + col_offset, value=value)
        print(f"Total recall has been updated in {excel_file}")

    wb.save(excel_file)


def extract_and_copy_files(src, dst, file_list):
    if not os.path.exists(src):
        logger.error(f"路径 {src} 不存在")
        return

    src = os.path.abspath(src)
    dst = os.path.abspath(dst)

    # 遍历src文件夹
    for root, dirs, files in os.walk(src):
        # 计算当前文件夹相对于src文件夹的相对路径
        rel_path = os.path.relpath(root, src)
        dst_dir = os.path.join(dst, rel_path)

        # 遍历当前文件夹中的文件
        for file in files:
            if file in file_list:
                src_file_path = os.path.join(root, file)
                dst_file_path = os.path.join(dst_dir, file)
                if not os.path.exists(dst_dir):
                    os.makedirs(dst_dir)
                # 复制文件
                shutil.copy2(src_file_path, dst_file_path)
                print(f"Copied: {src_file_path} to {dst_file_path}")

        

if __name__ == '__main__':
    # spec_file = 'demo3.spec'
    # build_requires, requires, sources = parse_spec_file(spec_file)
    #
    # print("BuildRequires:")
    # print(build_requires)
    # estimate_result("package-mapping-result.yaml", "spec.spec")

    # depends_path = "D:\Iron\Work\spec-test\Test\openmp\openmp-17.0.6.src\package-mapping-result.yaml"
    # spec_path = "D:\Iron\Work\spec-test\Test\openmp\openmp-17.0.6.src\*.spec"
    # estimate_result(depends_path, spec_path, "D:\Iron\Work\spec-test\Test\estimate.xlsx")
    os_version = "22.03-LTS-SP4"
    depends_path = "../Test/openmp/openmp-17.0.6.src/package-mapping-result.yaml"
    spec_path = "../Test/openmp/openmp-17.0.6.src/*.spec"
    estimate_result(depends_path, spec_path, "../estimate.xlsx", os_version)
    


